/***************************************************************************
 *   hmp.c                                                                 *
 *   Created on 9 wrzesień 2009, 10:56                                     *
 *   Copyright (C) 2009 by cmaster.matso (Maciej Kadłubowski)              *
 *   cmaster.matso@gmail.com                                               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "hmp.h"
#include "mgpi_roeskva.h"

static float s_nXWrap = 200.0f;
static float s_nYWrap = 200.0f;

/**
 * Gets a height value form the map sotred in the pixel.
 */
static Uint32 s_getPixel(SDL_Surface *surface, int x, int y) {
    int bpp = surface->format->BytesPerPixel;

    Uint8 *p = (Uint8 *) surface->pixels + y * surface->pitch + x * bpp;

    switch (bpp) {
        case 1:
            return *p;

        case 2:
            return *(Uint16 *) p;

        case 3:
            if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
                return p[0] << 16 | p[1] << 8 | p[2];
            else
                return p[0] | p[1] << 8 | p[2] << 16;

        case 4:
            return *(Uint32 *) p;

        default:
            return 0; /* shouldn't happen, but avoids warnings */
    }
}

/**
 * Loads a height map and prepares it's vertices, texture coordinates and normals arrays.
 */
int gsubReadHMPModel(const char *filename, hmp_t *hmp) {
    SDL_Surface *image;
    int x, y, num_vertices, num_indices, dx, dy, i = 0, i0, i1, i2;
    unsigned int ll, lr, tl, tr;
    float w, h;
    float4 side1, side2, normal = MGPI_DEFAULT_VECTOR, tangent = MGPI_DEFAULT_VECTOR;

    image = (SDL_Surface *)IMG_Load(filename);

    if (!image) {
        MGPI_PostMessagef("%s: couldn't open \"%s\"", __FUN__, filename);
        return 0;
    }

    num_vertices = (int)(image->w * image->h);
    num_indices = (int)((image->w - 1)*(image->h - 1) * 6);

    hmp->index_array = (unsigned int *)calloc(num_indices, sizeof(unsigned int));
    hmp->normal_array = (float3 *)calloc(num_vertices, sizeof(float3));
    hmp->tangent_array = (float3 *)calloc(num_vertices, sizeof(float3));
    hmp->vertex_array = (float4 *)calloc(num_vertices, sizeof(float4));
    hmp->tex_coords_array = (float2 *)calloc(num_vertices, sizeof(float2));
    hmp->num_vertices = num_vertices;
    hmp->num_indices = num_indices;
    hmp->width = (float)image->w;
    hmp->height = (float)image->h;
    
    w = (float)(image->w / s_nXWrap);
    h = (float)(image->h / s_nYWrap);

    for (y = 0; y < image->h; ++y)
        for (x = 0; x < image->w; ++x) {
            i = x + y * image->w;
            hmp->vertex_array[i][X] = (float)x;
            hmp->vertex_array[i][Y] = (float)s_getPixel(image, x, y);
            hmp->vertex_array[i][Z] = (float)-y;
            hmp->vertex_array[i][W] = 1.0f;

            for (dx = x; dx < (x + w); ++dx)
                hmp->tex_coords_array[i][X] = (dx / w);
            for (dy = y; dy < (y + h); ++dy)
                hmp->tex_coords_array[i][Y] = (dy / h);
        }

    i = 0;

    for (y = 0; y < (image->h - 1); ++y)
        for (x = 0; x < (image->w - 1); ++x) {
            ll = (unsigned int)(x + y * (int)image->w);
            lr = (unsigned int)((x + 1) + y * (int)image->w);
            tl = (unsigned int)(x + (y + 1) * (int)image->w);
            tr = (unsigned int)((x + 1) + (y + 1) * (int)image->w);

            hmp->index_array[i++] = tl;
            hmp->index_array[i++] = lr;
            hmp->index_array[i++] = ll;/*tr;*/
            
            hmp->index_array[i++] = tl;
            hmp->index_array[i++] = tr;/*ll;*/
            hmp->index_array[i++] = lr;
        }

    for (i = 0; i < num_indices / 3; ++i) {
        i0 = hmp->index_array[i * 3];
        i1 = hmp->index_array[i * 3 + 1];
        i2 = hmp->index_array[i * 3 + 2];

        side1[X] = hmp->vertex_array[i0][X] - hmp->vertex_array[i2][X];
        side1[Y] = hmp->vertex_array[i0][Y] - hmp->vertex_array[i2][Y];
        side1[Z] = hmp->vertex_array[i0][Z] - hmp->vertex_array[i2][Z];
        side1[W] = 1.0f;

        side2[X] = hmp->vertex_array[i0][X] - hmp->vertex_array[i1][X];
        side2[Y] = hmp->vertex_array[i0][Y] - hmp->vertex_array[i1][Y];
        side2[Z] = hmp->vertex_array[i0][Z] - hmp->vertex_array[i1][Z];
        side2[W] = 1.0f;

        MGPI_CrossProduct(&normal, side1, side2);
        MGPI_Normalize(&normal);
        
        hmp->normal_array[i0][X] = normal[X];
        hmp->normal_array[i0][Y] = normal[Y];
        hmp->normal_array[i0][Z] = normal[Z];

        hmp->normal_array[i1][X] = normal[X];
        hmp->normal_array[i1][Y] = normal[Y];
        hmp->normal_array[i1][Z] = normal[Z];

        hmp->normal_array[i2][X] = normal[X];
        hmp->normal_array[i2][Y] = normal[Y];
        hmp->normal_array[i2][Z] = normal[Z];
    }

    /* TODO: place here normals averaging code */

    for (i = 0; i < num_indices / 3; ++i) {
        float2 tb1, tb2;
        float denom = 0.0f;

        i0 = hmp->index_array[i * 3];
        i1 = hmp->index_array[i * 3 + 1];
        i2 = hmp->index_array[i * 3 + 2];

        side1[X] = hmp->vertex_array[i0][X] - hmp->vertex_array[i2][X];
        side1[Y] = hmp->vertex_array[i0][Y] - hmp->vertex_array[i2][Y];
        side1[Z] = hmp->vertex_array[i0][Z] - hmp->vertex_array[i2][Z];
        side1[W] = 1.0f;

        side2[X] = hmp->vertex_array[i1][X] - hmp->vertex_array[i2][X];
        side2[Y] = hmp->vertex_array[i1][Y] - hmp->vertex_array[i2][Y];
        side2[Z] = hmp->vertex_array[i1][Z] - hmp->vertex_array[i2][Z];
        side2[W] = 1.0f;

        tb1[X] = hmp->tex_coords_array[i0][X] - hmp->tex_coords_array[i2][X];
        tb1[Y] = hmp->tex_coords_array[i0][Y] - hmp->tex_coords_array[i2][Y];

        tb2[X] = hmp->tex_coords_array[i1][X] - hmp->tex_coords_array[i2][X];
        tb2[Y] = hmp->tex_coords_array[i1][Y] - hmp->tex_coords_array[i2][Y];

        denom = tb1[X] * tb2[Y] - tb2[X] * tb1[Y];

        if (denom != 0.0f) {
            float scale = 1.0f / denom;

            tangent[X] = (tb2[Y] * side1[X] - tb1[Y] * side2[X]) * scale;
            tangent[Y] = (tb2[Y] * side1[Y] - tb1[Y] * side2[Y]) * scale;
            tangent[Z] = (tb2[Y] * side1[Z] - tb1[Y] * side2[Z]) * scale;
            tangent[W] = 1.0f;

            MGPI_Normalize(&tangent);

            hmp->tangent_array[i0][X] = tangent[X];
            hmp->tangent_array[i0][Y] = tangent[Y];
            hmp->tangent_array[i0][Z] = tangent[Z];

            hmp->tangent_array[i1][X] = tangent[X];
            hmp->tangent_array[i1][Y] = tangent[Y];
            hmp->tangent_array[i1][Z] = tangent[Z];

            hmp->tangent_array[i2][X] = tangent[X];
            hmp->tangent_array[i2][Y] = tangent[Y];
            hmp->tangent_array[i2][Z] = tangent[Z];
        }
    }

    return (!0);
}

/**
 * Draws the height map to the srceen.
 */
int gsubRenderHMPModel(const hmp_t *hmp) {
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glEnableClientState(GL_SECONDARY_COLOR_ARRAY);

    glVertexPointer(4, GL_FLOAT, 0, hmp->vertex_array);
    glNormalPointer(GL_FLOAT, 0, hmp->normal_array);
    glTexCoordPointer(2, GL_FLOAT, 0, hmp->tex_coords_array);
    glSecondaryColorPointer(3, GL_FLOAT, 0, hmp->tangent_array);

    glDrawElements(GL_TRIANGLES, hmp->num_indices, GL_UNSIGNED_INT, hmp->index_array);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_SECONDARY_COLOR_ARRAY);

    return (!0);
}

/**
 * Sets the height map's texture coordinates wrapping farctors.
 */
void gsubSetTexCoordsWrapping(float x, float y) {
    s_nXWrap = x;
    s_nYWrap = y;
    return;
}

/**
 *
 */
MGPI_BOOL gsubIsOnMapv(hmp_t *hmp, float2 position) {

    /* TODO: implement */

    return MGPI_TRUE;
}

/**
 *
 */
MGPI_BOOL gsubIsOnMapf(hmp_t *hmp, float x, float y)
{
    /* TODO: implement */

    return MGPI_TRUE;
}

/**
 *
 */
void gsubGetNormalv(hmp_t *hmp, float4 *pnormal, float2 position) {

    /* TODO: implement */

    return;
}

/**
 *
 */
void gsubGetNormalf(hmp_t *hmp, float4 *pnormal, float x, float y) {

    /* TODO: implement */

    return;
}

/**
 *
 */
void gsubGetHeightv(hmp_t *hmp, float *pheight, float2 position) {

    /* TODO: implement */

    return;
}

/**
 *
 */
void gsubGetHeightf(hmp_t *hmp, float *pheight, float x, float y) {

    /* TODO: implement */

    return;
}

/**
 * Frees the height map object.
 */
void gsubFreeHMPModel(hmp_t *hmp) {
    if (hmp) {
        if (hmp->index_array) {
            free(hmp->index_array);
            hmp->index_array = NULL;
        }
        if (hmp->normal_array) {
            free(hmp->normal_array);
            hmp->normal_array = NULL;
        }
        if (hmp->tangent_array) {
            free(hmp->tangent_array);
            hmp->tangent_array = NULL;
        }
        if (hmp->tex_coords_array) {
            free(hmp->tex_coords_array);
            hmp->tex_coords_array = NULL;
        }
        if (hmp->vertex_array) {
            free(hmp->vertex_array);
            hmp->vertex_array = NULL;
        }
    }
    return;
}
